/**
 * 
 */
package bsh;

import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import bsh.asm.acessor.ObjectAccessor;
import bsh.compilation.ReflectionUtilsImpl;
import bsh.meta.JavaClassWrapper;
import bsh.meta.MethodDescriptor;
import bsh.meta.TypeWrapper;
import bsh.utils.reflection.BytecodeHelper;

/**
 * {@link ObjectAccessor} that compiles to {@link CompiledScriptHelper#getFieldOrProperty(Object, String, int, CompiledScript)} and
 * {@link CompiledScriptHelper#setFieldOrProperty(Object, String, int, Object, CompiledScript)}
 * 
 * @author takeshi
 */
public class DynamicFieldObjectAccessor implements ObjectAccessor {

    private final String propertyName;

    /**
     * @param propertyName the property name
     */
    public DynamicFieldObjectAccessor(String propertyName) {
        super();
        this.propertyName = propertyName;
    }

    public static final MethodDescriptor GETTER, SETTER;
    static {
        ReflectionUtils utils = new ReflectionUtilsImpl();
        GETTER =
                utils.findMethodForTypes(JavaClassWrapper.wrapperFor(CompiledScriptHelper.class), "getFieldOrProperty",
                        new TypeWrapper[]{ JavaClassWrapper.OBJECT_WRAPPER, JavaClassWrapper.STRING_WRAPPER, JavaClassWrapper.INT_WRAPPER,
                                JavaClassWrapper.wrapperFor(CompiledScript.class) });
        SETTER =
                utils.findMethodForTypes(JavaClassWrapper.wrapperFor(CompiledScriptHelper.class), "setFieldOrProperty", new TypeWrapper[]{
                        JavaClassWrapper.OBJECT_WRAPPER, JavaClassWrapper.STRING_WRAPPER, JavaClassWrapper.INT_WRAPPER,
                        JavaClassWrapper.OBJECT_WRAPPER, JavaClassWrapper.wrapperFor(CompiledScript.class) });
    }

    /**
     * {@inheritDoc}
     */
    public void loadObject(MethodVisitor visitor, CompilationState state) {
        // we just push the args and invoke
        // at least 1 element should be here
        state.assertMinimumStackSize(1);
        int stack = state.getStackSize();
        visitor.visitLdcInsn(propertyName);
        state.pushStack(String.class);
        int id = state.getUnit().thisType().newDynamicInvocationId();
        BytecodeHelper.pushIntConst(id, visitor);
        state.pushStack(int.class);
        visitor.visitVarInsn(Opcodes.ALOAD, 0);
        state.pushStack(state.getUnit().thisType());

        GETTER.writeInvoke(visitor, state);

        state.assertStackSize(stack);
    }

    /**
     * {@inheritDoc}
     */
    public void preStore(MethodVisitor visitor, CompilationState state) {
        // maintenance work. This happens before the value to be stored is stacked
        visitor.visitLdcInsn(propertyName);
        state.pushStack(String.class);
        int id = state.getUnit().thisType().newDynamicInvocationId();
        BytecodeHelper.pushIntConst(id, visitor);
        state.pushStack(int.class);
    }

    /**
     * {@inheritDoc}
     */
    public boolean readOnly() {
        return false;
    }

    /**
     * {@inheritDoc}
     */
    public void storeObject(MethodVisitor visitor, CompilationState state) {
        // the stack must have: the store target, the field name, the callsiteId and the value to be stored
        final int min = 4;
        state.assertMinimumStackSize(min);
        visitor.visitVarInsn(Opcodes.ALOAD, 0);
        state.pushStack(state.getUnit().thisType());
        SETTER.writeInvoke(visitor, state);
    }

    /**
     * {@inheritDoc}
     */
    public TypeWrapper type() {
        return JavaClassWrapper.OBJECT_WRAPPER;
    }

}